#include "types.h"
#include "globals.h"
#include "errno.h"

#include "util/debug.h"
#include "util/string.h"

#include "proc/proc.h"
#include "proc/kthread.h"

#include "mm/mm.h"
#include "mm/mman.h"
#include "mm/page.h"
#include "mm/pframe.h"
#include "mm/mmobj.h"
#include "mm/pagetable.h"
#include "mm/tlb.h"

#include "fs/file.h"
#include "fs/vnode.h"

#include "vm/shadow.h"
#include "vm/vmmap.h"

#include "api/exec.h"

#include "main/interrupt.h"

/* Pushes the appropriate things onto the kernel stack of a newly forked thread
 * so that it can begin execution in userland_entry.
 * regs: registers the new thread should have on execution
 * kstack: location of the new thread's kernel stack
 * Returns the new stack pointer on success. */
static uint32_t
fork_setup_stack(const regs_t *regs, void *kstack)
{
        /* Pointer argument and dummy return address, and userland dummy return
         * address */
        uint32_t esp = ((uint32_t) kstack) + DEFAULT_STACK_SIZE - (sizeof(regs_t) + 12);
        *(void **)(esp + 4) = (void *)(esp + 8); /* Set the argument to point to location of struct on stack */
        memcpy((void *)(esp + 8), regs, sizeof(regs_t)); /* Copy over struct */
        return esp;
}


/*
 * The implementation of fork(2). Once this works,
 * you're practically home free. This is what the
 * entirety of Weenix has been leading up to.
 * Go forth and conquer.
 */
int
do_fork(struct regs *regs)
{
	KASSERT(regs != NULL);
        KASSERT(curproc != NULL);
        KASSERT(curproc->p_state == PROC_RUNNING);
	dbg(DBG_TEST, "KASSERT PASS\n");

	int n = 0;
	proc_t *new_proc = NULL;
	kthread_t *new_thread = NULL;
	vmmap_t *new_vmmap = NULL;
	vmarea_t *parent_vmarea = NULL;
	vmarea_t *child_vmarea = NULL;
	vmarea_t *fault_vmarea = NULL;
	mmobj_t *parent_mmobj = NULL;
	mmobj_t *new_shadow_parent = NULL;
	mmobj_t *new_shadow_child = NULL;

	/* operation on new process control block */
	new_proc = proc_create("child");
	vmmap_destroy(new_proc->p_vmmap);
	new_proc->p_vmmap = NULL;

	/* operation on mmap and page table */
	new_vmmap = vmmap_clone(curproc->p_vmmap);
	if (new_vmmap == NULL) {
		return -ENOMEM;
	}
	list_iterate_begin(&(curproc->p_vmmap->vmm_list), parent_vmarea, vmarea_t, vma_plink) {
		parent_mmobj = parent_vmarea->vma_obj;
		child_vmarea = vmmap_lookup(new_vmmap, parent_vmarea->vma_start);
		if (child_vmarea == NULL) {
			panic("there is something wrong in vmmap_clone\n");
		}

		if (parent_vmarea->vma_flags & MAP_PRIVATE) {
			/* create new shadow for this */
			new_shadow_parent = shadow_create();
			new_shadow_child = shadow_create();
			if ((new_shadow_parent == NULL) || (new_shadow_child == NULL)) {
				/* clean up all the changes in this function */
				if (new_shadow_parent != NULL) {
					new_shadow_parent->mmo_ops->put(new_shadow_parent);
				}
				if (new_shadow_child != NULL) {
					new_shadow_parent->mmo_ops->put(new_shadow_child);
				}
				vmmap_destroy(new_vmmap);
				/* we do not care changes in parent vmmap, since shadowd will
				 * clean it for us
				 */
				return -ENOMEM;
			}
			new_shadow_parent->mmo_un.mmo_bottom_obj = parent_mmobj->mmo_un.mmo_bottom_obj;
			new_shadow_parent->mmo_shadowed = parent_mmobj;

			new_shadow_child->mmo_un.mmo_bottom_obj = parent_mmobj->mmo_un.mmo_bottom_obj;
			new_shadow_child->mmo_shadowed = parent_mmobj;

			parent_mmobj->mmo_ops->ref(parent_mmobj);

			parent_vmarea->vma_obj = new_shadow_parent;
			child_vmarea->vma_obj = new_shadow_child;
			list_insert_before(&parent_vmarea->vma_olink, &child_vmarea->vma_olink);
		}
		if (parent_vmarea->vma_flags & MAP_SHARED) {
			/* both of the vmmarea will point ot the same mmobj */
			child_vmarea->vma_obj = parent_mmobj;
			list_insert_head(&parent_mmobj->mmo_un.mmo_vmas, &child_vmarea->vma_olink);
			parent_mmobj->mmo_ops->ref(parent_mmobj);
		}
	}list_iterate_end();

	new_proc->p_vmmap = new_vmmap;
	new_vmmap->vmm_proc = new_proc;
	pt_unmap_range(curproc->p_pagedir, USER_MEM_LOW, USER_MEM_HIGH);
	tlb_flush_all();


	/* operation on file table */
	for (n = 0; n < NFILES; n++) {
		if (curproc->p_files[n] != NULL) {
			new_proc->p_files[n] = curproc->p_files[n];
			fref(curproc->p_files[n]);
		}
	}
	

	/* operation on working dirctory*/
	/* this has already done in proc_create */
	
	/* operation on thread */
	/* due to single thread */
	new_thread = kthread_clone(curthr);
	if (new_thread == NULL) {
		vmmap_destroy(new_vmmap);
		return -ENOMEM;
	}
	new_thread->kt_proc = new_proc;
	list_insert_head(&(new_proc->p_threads), &(new_thread->kt_plink));
	/* operation on context */
	regs->r_eax = 0; /* this is the return value of child */
	new_thread->kt_ctx.c_pdptr = new_proc->p_pagedir;
	new_thread->kt_ctx.c_eip = (uint32_t)&userland_entry;
	new_thread->kt_ctx.c_kstack = (uintptr_t)(new_thread->kt_kstack);
	new_thread->kt_ctx.c_kstacksz = DEFAULT_STACK_SIZE;
	new_thread->kt_ctx.c_esp = fork_setup_stack(regs, new_thread->kt_kstack);
	sched_make_runnable(new_thread);

        KASSERT(new_proc->p_state == PROC_RUNNING);
        KASSERT(new_proc->p_pagedir != NULL);
        KASSERT(new_thread->kt_kstack != NULL);
	dbg(DBG_TEST, "KASSERT PASS\n");

	regs->r_eax = new_proc->p_pid; /* this is the return value of parent */
	return new_proc->p_pid;
}






















